﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.ServiceModel;
using System.Threading;
using log4net;
using PriceLibrary.Enums;
using PriceLibrary.PriceService;
using System.Timers;
using Timer = System.Timers.Timer;
using System.Diagnostics;

namespace PriceLibrary.Utilities
{
    public delegate void KeyChange(Guid newKey);

    public delegate void PriceChange(IDictionary<string, StockBase> stockBases);

    public delegate void BasePriceChange(IDictionary<string, StockBase> stockBases);

    public delegate void TickerChange(IList<string> newTickers);

    public delegate void MarketInfoChange(IDictionary<int, StockBase> maketInfos);

    public delegate void ReceivedNewData(IDictionary<string, StockBase> basePrices, IDictionary<string, StockBase> top3Prices);

    public delegate void TickerInfo(string ticker);

    public delegate void TickerPrice(string ticker);

    public delegate void TickerTechnique(string ticker);

    public delegate void StockBaseChanged(IList<StockBase> newStockBase);

    public delegate void ChangeServiceStatus(bool isConnected);

    public class PriceCallBack : IServiceCallback
    {
        #region Members and Properties

        public bool IsConnected = true;
        public static bool IsInited = false;
        private readonly ILog _log = LogManager.GetLogger(typeof(PriceCallBack));
        
        private Timer _timer; // Dùng để tạo khoản request đến Service, giữ connection (keep live)
        private IDictionary<string, StockBase> _basePrices;
        private IDictionary<string, StockBase> _top3Prices;
        private IList<string> _tickers;
        private IDictionary<int, StockBase> _marketInfos;

        public Guid Key { get; set; }


        private IList<StockBase> _ptTopPrices;
        public IList<StockBase> PtTopPrices
        {
            get
            {
                if (_ptTopPrices==null)
                {
                    _ptTopPrices= new List<StockBase>();
                }
                return _ptTopPrices;
            }
            set { _ptTopPrices = value; }
        }

        private IList<StockBase> _ptTransactions;
        public IList<StockBase> PtTransactions
        {
            get
            {
                if(_ptTransactions==null)
                {
                    _ptTransactions= new List<StockBase>();
                }
                    return _ptTransactions;
            }
            set { _ptTransactions = value; }
        }


        public IList<string> Tickers
        {
            get { return _tickers ?? (_tickers = new List<string>()); }
            set
            {
                _tickers = value;
                //if (PriceServer!=null)
                //{
                //    PriceServer.SubscribeSymbols((List<string>) value);
                //}
                //InvokeClientTickerChanged(Tickers);
            }
        }
        public IList<string> CeilingTickers { get; set; }
        public IList<string> FloorTickers { get; set; }

        public IDictionary<string, StockBase> CeilingFloorBasePrices { get; set; }
        public IDictionary<string, StockBase> CeilingFloorTop3Prices { get; set; }

        private IService _priceServer;
        public IService PriceServer
        {
            get
            {
                Callback_Reconnect(null,null);
                return _priceServer;
            }
            set
            {
                try
                {
                    //Connection State
                    _priceServer = value;
                }
                catch (Exception ex)
                {
                    _log.Error("Error when subcribes key and stock symbols", ex);
                    _log.Info(Key);
                    _log.Info(Tickers);
                }
            }
        }

        private void Callback_Reconnect(object sender, EventArgs e)
        {
            int count = 0;
            while (_priceServer == null ||
                ((((ICommunicationObject)_priceServer).State == CommunicationState.Faulted
                || ((ICommunicationObject)_priceServer).State == CommunicationState.Closed)
                && count < 2)
                )
            {
                try
                {
                    _priceServer = ChannelFactory.CreateChannel(new InstanceContext(this));
                    if (((ICommunicationObject)_priceServer).State != CommunicationState.Opened)
                    {
                        ((ICommunicationObject)_priceServer).Open();
                    }
                    //if (!IsInited)
                    //{
                    //    _priceServer.SubscribeNewSymbols((List<string>)this.Tickers);
                    //    _priceServer.GetMarketInfos(true);
                    //}
                    _priceServer.SubscribeNewSymbols((List<string>)this.Tickers);
                    _priceServer.GetMarketInfos(true);
                    ((ICommunicationObject)_priceServer).Faulted += Callback_Reconnect;
                }
                catch (Exception)
                {
                    Thread.Sleep(1000);
                    count++;
                }
                if (count >= 2)
                {
                    _log.Fatal("Khong the connect den Server!");
                    //InvokeServiceStatusChanged(false); 
                   // throw new ArgumentException("Can not connect to PriceServer!", "PriceCallBack");
                }
            }
        }


        private ServiceClient ServerClient
        {
            get { return (ServiceClient)PriceServer; }
        }

        public IDictionary<string, StockBase> BasePrices
        {
            get { return _basePrices; }
            set
            {
                _basePrices = value;
                InvokeBasePriceChanged(value);
            }
        }

        public IDictionary<string, StockBase> Top3Prices
        {
            get { return _top3Prices; }
            set
            {
                _top3Prices = value;
                InvokeTop3PriceChanged(value);
            }
        }

        public IDictionary<int, StockBase> MarketInfos
        {
            get { return _marketInfos; }
            set { _marketInfos = value; }
        }


        protected DuplexChannelFactory<IService> ChannelFactory { get; set; }

        #endregion

        #region Events

        #region KeyOfClientChanged
        /// <summary>
        /// Sự kiện khi client đăng ký thành công và nhận Key từ phía server
        /// </summary>
        public event KeyChange KeyOfClientChanged;
        private void InvokeKeyOfClientChanged()
        {
            KeyChange handler = KeyOfClientChanged;
            if (handler != null) handler(Key);
        }
        #endregion KeyOfClientChanged

        #region BasePriceChanged
        /// <summary>
        /// Chỉ số giá trần, sàn,.. thay đổi ( Ít sử dụng)
        /// </summary>
        public event BasePriceChange BasePriceChanged;
        private void InvokeBasePriceChanged(IDictionary<string, StockBase> basePrices)
        {
            BasePriceChange handler = BasePriceChanged;
            if (handler != null) handler(basePrices);
        }
        #endregion

        #region EndSendStockData
        public event ReceivedNewData EndSendStockData;

        private void InvokeEndSendStockData(IDictionary<string, StockBase> basePrices, IDictionary<string, StockBase> top3Prices)
        {
            ReceivedNewData handler = EndSendStockData;
            if (handler != null) handler(basePrices, top3Prices);
        }
        #endregion EndSendStockData

        #region RemoveTickers
        /// <summary>
        /// Sự kiện khi có thay đổi về danh sách ticker của client
        /// </summary>
        public event TickerChange RemoveTickers;
        private void InvokeRemoveTickers(IList<string> tickers)
        {
            TickerChange handler = RemoveTickers;
            if (handler != null) handler(tickers);
        }
        #endregion

        #region EndReceiveCeilingTickers
        /// <summary>
        /// Occurs when [end receive ceiling tickers].
        /// </summary>
        public event TickerChange EndReceiveCeilingTickers;
        private void InvokeEndReceiveCeilingTickers(IList<string> tickers)
        {
            TickerChange handler = EndReceiveCeilingTickers;
            if (handler != null) handler(tickers);
        }
        #endregion EndReceiveCeilingTickers

        #region EndReceiveFloorTickers
        /// <summary>
        /// Occurs when [end receive floor tickers].
        /// </summary>
        public event TickerChange EndReceiveFloorTickers;
        private void InvokeEndReceiveFloorTickers(IList<string> tickers)
        {
            TickerChange handler = EndReceiveFloorTickers;
            if (handler != null) handler(tickers);
        }
        #endregion EndReceiveFloorTickers

        #region Top3PriceChanged
        /// <summary>
        /// Chỉ số 3 giá thay đổi, cả thông tin về giá lệnh khớp, KL, GT,...
        /// </summary>
        public event PriceChange Top3PriceChanged;
        private void InvokeTop3PriceChanged(IDictionary<string, StockBase> top3PriceChanged)
        {
            PriceChange handler = Top3PriceChanged;
            if (handler != null) handler(top3PriceChanged);
        }
        #endregion

        #region MarketInfoChanged
        /// <summary>
        /// Occurs when [market info changed].
        /// </summary>
        public event MarketInfoChange MarketInfoChanged;
        private void InvokeMarketInfoChanged(IDictionary<int, StockBase> marketInfos)
        {
            MarketInfoChange handler = MarketInfoChanged;
            if (handler != null) handler(marketInfos);
        }
        #endregion

        #region EndReceivedNewTickers

        public event TickerChange EndReceivedNewTickers;

        private void InvokeEndReceivedNewTickers(IList<string> tickers)
        {
            TickerChange handler = EndReceivedNewTickers;
            if (handler != null) handler(tickers);
        }

        #endregion EndReceivedNewTickers

        #region EndReceivedCeilingFloorData

        private event ReceivedNewData EndReceivedCeilingFloorData;

        private void InvokeEndReceivedCeilingFloorData(IDictionary<string, StockBase> basePrices, IDictionary<string, StockBase> top3Prices)
        {
            ReceivedNewData handler = EndReceivedCeilingFloorData;
            if (handler != null) handler(basePrices, top3Prices);
        }

        #endregion EndReceivedCeilingFloorData

        #region EndReceivedPtTopPrices
        public event StockBaseChanged EndReceivedPtTopPrices;

        protected void InvokeEndReceivedPtTopPrices(IList<StockBase> newStockBases)
        {
            StockBaseChanged handler = EndReceivedPtTopPrices;
            if (handler != null) handler(newStockBases);
        } 
        #endregion

        #region EndReceivedPtTransactions
        public event StockBaseChanged EndReceivedPtTransactions;

        protected void InvokeEndReceivedPtTransactions(IList<StockBase> newStockBases)
        {
            StockBaseChanged handler = EndReceivedPtTransactions;
            if (handler != null) handler(newStockBases);
        } 
        #endregion


        public event ChangeServiceStatus ServiceStatusChanged;
        protected void InvokeServiceStatusChanged(bool isConnected)
        {
            ChangeServiceStatus handler = ServiceStatusChanged;
            if (handler != null) handler(isConnected);
        }

        #endregion

        #region Contructors

        /// <summary>
        /// Initializes a new instance of the <see cref="PriceCallBack"/> class.
        /// Khoi tao voi URL duoc thiet lap trong appSettings["PriceUrl"]
        /// </summary>
        [Obsolete("Please use PriceCallBack(url). Default used: AppSettings[PriceUrl]")]
        public PriceCallBack()
        {
            BasePrices = new Dictionary<string, StockBase>();
            Top3Prices = new Dictionary<string, StockBase>();
            MarketInfos = new Dictionary<int, StockBase>();
            _tickers = new List<string>();

            CeilingFloorBasePrices = new Dictionary<string, StockBase>();
            CeilingFloorTop3Prices = new Dictionary<string, StockBase>();

            _timer = new Timer(60 * 1000);
            _timer.Elapsed += KeepLive;


            //Cần modify lại, nếu ko phải lần đầu thi lấy Key đã từng connect
            // Key sẽ lưu ở phía client
            Key = Guid.NewGuid();

            if (ConfigurationManager.AppSettings["PriceUrl"] != null) Init(ConfigurationManager.AppSettings["PriceUrl"]);

        }

        public PriceCallBack(string url)
        {
            BasePrices = new Dictionary<string, StockBase>();
            Top3Prices = new Dictionary<string, StockBase>();
            MarketInfos = new Dictionary<int, StockBase>();
            _tickers = new List<string>();

            CeilingFloorBasePrices = new Dictionary<string, StockBase>();
            CeilingFloorTop3Prices = new Dictionary<string, StockBase>();

            _timer = new Timer(60 * 1000);
            _timer.Elapsed += KeepLive;


            //Cần modify lại, nếu ko phải lần đầu thi lấy Key đã từng connect
            // Key sẽ lưu ở phía client
            Key = Guid.NewGuid();

            if (url != null) Init(url);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PriceCallBack"/> class.
        /// </summary>
        /// <param name="priceServer">The price server.</param>
        [Obsolete("Please use PriceCallBack() or PriceCallBack(url)! Not used anymore.")]
        public PriceCallBack(IService priceServer)
        {
            BasePrices = new Dictionary<string, StockBase>();
            Top3Prices = new Dictionary<string, StockBase>();
            _tickers = new List<string>();

            PriceServer = priceServer;
            //PriceServer.Open();
            Key = Guid.NewGuid();

            //PriceServer.Subscribe(Key, (List<string>)Tickers);

        }

        #endregion

        #region Implementation of PriceServerCallback

        public void ConfirmSubscriber(Guid key)
        {
            Key = key;
            InvokeKeyOfClientChanged();
            _log.Info(string.Format("Confirm subcriber key {0}", Key));
        }

        public void SendBasePrices(Dictionary<string, StockBase> basePrices)
        {
            _log.Info(string.Format("Client {0} received base prices!", Key));
            if (basePrices != null)
            {
                foreach (KeyValuePair<string, StockBase> keyValuePair in basePrices)
                {
                    if (!BasePrices.ContainsKey(keyValuePair.Key))
                    {
                        BasePrices.Add(keyValuePair.Key, keyValuePair.Value);
                    }
                    else
                    {
                        BasePrices[keyValuePair.Key] = keyValuePair.Value;
                    }
                }
            }
            InvokeBasePriceChanged(basePrices);
        }

        public void SendTickers(List<string> tickers)
        {
            if (tickers != null)
            {
                InvokeEndReceivedNewTickers(tickers);
            }
        }

        public void SendTop3Prices(Dictionary<string, StockBase> top3Prices)
        {
            if (top3Prices != null)
                foreach (KeyValuePair<string, StockBase> keyValuePair in top3Prices)
                {
                    if (Top3Prices.ContainsKey(keyValuePair.Key))
                    {
                        Top3Prices[keyValuePair.Key] = keyValuePair.Value;
                    }
                    else
                    {
                        Top3Prices.Add(keyValuePair.Key, keyValuePair.Value);
                    }
                }
            InvokeTop3PriceChanged(top3Prices);
            _log.Info(string.Format("Client {0} received top3 prices!", Key));
        }

        public void SendPtTopPrices(List<StockBase> ptTopPrices)
        {
            foreach (StockBase ptTopPrice in ptTopPrices)
            {
                var info = ptTopPrice as StockInfoDyns;
                if (info!=null )
                {
                    if (info.Status==1) //Add
                    {
                        PtTopPrices.Add(info);
                    }
                    else
                    {
                        StockInfoDyns tmp = null;
                        foreach (StockInfoDyns stockInfoDyn in PtTopPrices)
                        {
                            if (info.StockSymbol==stockInfoDyn.StockSymbol && info.StockNo==stockInfoDyn.StockNo 
                                && info.Last==stockInfoDyn.Last
                                && info.LastVol==stockInfoDyn.LastVol
                                )
                            {
                                tmp = stockInfoDyn;
                                break;
                            }
                        }
                        if (tmp != null) PtTopPrices.Remove(tmp);
                    }
                }
            }
            InvokeEndReceivedPtTopPrices(ptTopPrices);
        }

        public void SendPtTransaction(List<StockBase> ptTransaction)
        {
            foreach (StockBase stockBase in ptTransaction)
            {
                PtTransactions.Add(stockBase);
            }
            InvokeEndReceivedPtTransactions(ptTransaction);
        }

        public void SendMarketData(Dictionary<int, StockBase> marketInfos)
        {
            if (marketInfos != null)
            {
                foreach (KeyValuePair<int, StockBase> keyValuePair in marketInfos)
                {
                    if (MarketInfos.ContainsKey(keyValuePair.Key))
                    {
                        MarketInfos[keyValuePair.Key] = keyValuePair.Value;
                    }
                    else
                    {
                        MarketInfos.Add(keyValuePair.Key, keyValuePair.Value);
                    }
                }
                //_marketInfos = marketInfos;

                InvokeMarketInfoChanged(marketInfos);
                _log.Info(string.Format("Client {0} received market info!", Key));
            }
        }

        public void SendTop3PriceChanged(Dictionary<string, StockBase> top3PriceChanged)
        {
            foreach (KeyValuePair<string, StockBase> keyValuePair in top3PriceChanged)
            {
                if (Top3Prices.ContainsKey(keyValuePair.Key))
                {
                    Top3Prices[keyValuePair.Key] = keyValuePair.Value;
                }
                else
                {
                    Top3Prices.Add(keyValuePair.Key, keyValuePair.Value);
                }
            }
            InvokeTop3PriceChanged(top3PriceChanged);
        }

        //public void SendBasePrice(StockInfoStatic infoStatic)
        //{
        //    if (infoStatic != null && !BasePrices.ContainsKey(infoStatic.StockSymbol))
        //    {
        //        BasePrices.Add(infoStatic.StockSymbol, infoStatic);
        //    }
        //}

        /// <summary>
        /// Sends the stock data (base prices and top3 prices).
        /// </summary>
        /// <param name="basePrices">The base prices.</param>
        /// <param name="top3Prices">The top3 prices.</param>
        public void SendStockData(Dictionary<string, StockBase> basePrices, Dictionary<string, StockBase> top3Prices)
        {
            foreach (KeyValuePair<string, StockBase> keyValuePair in basePrices)
            {
                if (!BasePrices.ContainsKey(keyValuePair.Key))
                {
                    BasePrices.Add(keyValuePair.Key, keyValuePair.Value);
                }
            }
            foreach (KeyValuePair<string, StockBase> keyValuePair in top3Prices)
            {
                if (!Top3Prices.ContainsKey(keyValuePair.Key))
                {
                    Top3Prices.Add(keyValuePair.Key, keyValuePair.Value);
                }
            }
            InvokeEndSendStockData(basePrices, top3Prices);

            _log.Info(string.Format("Client {0} received all stock info", Key));
        }

        //public void SendStockInfo(StockInfoStatic infoStatic, StockInfoDyns infoDyns)
        //{
        //    if (infoStatic!=null && infoDyns!=null && infoStatic.StockSymbol==infoDyns.StockSymbol)
        //    {
        //        if (!BasePrices.ContainsKey(infoStatic.StockSymbol) ) BasePrices.Add(infoStatic.StockSymbol,infoStatic);
        //        if (!Top3Prices.ContainsKey(infoDyns.StockSymbol)) Top3Prices.Add(infoDyns.StockSymbol,infoDyns);
        //    }
        //    InvokeEndReceivedNewStockInfo(infoStatic,infoDyns );
        //}

        public void ReceiveCeilingSymbols(List<string> symbols)
        {
            CeilingTickers = symbols;
            InvokeEndReceiveCeilingTickers(symbols);
        }

        /// <summary>
        /// Receives the ceiling floor data.
        /// </summary>
        /// <param name="basePrices">The base prices.</param>
        /// <param name="top3Prices">The top3 prices.</param>
        public void ReceiveCeilingFloorData(Dictionary<string, StockBase> basePrices, Dictionary<string, StockBase> top3Prices)
        {
            CeilingFloorBasePrices = basePrices;
            CeilingFloorTop3Prices = top3Prices;

            InvokeEndReceivedCeilingFloorData(basePrices, top3Prices);
        }

        public void ReceiveFloorSymbols(List<string> symbols)
        {
            FloorTickers = symbols;
            InvokeEndReceiveFloorTickers(symbols);
        }

        #endregion

        #region Tickers

        public void Clear()
        {
            PriceServer.UnsubscribeSymbols((List<string>)Tickers);
            Tickers.Clear();
            BasePrices.Clear();
            Top3Prices.Clear();
            //InvokeClientTickerChanged(Tickers);
        }

        public void Add(string ticker)
        {
            try
            {
                if (!Tickers.Contains(ticker))
                {
                    Tickers.Add(ticker);
                    PriceServer.SubscribeNewSymbols(new List<string> { ticker });
                }
            }
            catch (CommunicationException ce)
            {
                //ICommunicationObject communicationObject = PriceServer as ICommunicationObject;
                //if (communicationObject != null)
                //{
                //    communicationObject.Abort();
                //}
                throw;
            }
            catch (Exception ex)
            {
                _log.Error("Error when subcribes new tickers!", ex);
                throw;
            }

        }

        public void Remove(string ticker)
        {
            if (Tickers.Contains(ticker))
            {
                Tickers.Remove(ticker);

            }
            PriceServer.UnsubscribeSymbols(new List<string> { ticker });
            InvokeRemoveTickers(new List<string> { ticker });
        }
        #endregion

        #region Initialize
        protected void Init(string url)
        {
            NetTcpBinding netTcpBinding = new NetTcpBinding(SecurityMode.None);
            //netTcpBinding.MaxBufferPoolSize = int.MaxValue;
            netTcpBinding.MaxReceivedMessageSize = netTcpBinding.MaxBufferSize = int.MaxValue;
            netTcpBinding.MaxConnections = 30;

            netTcpBinding.OpenTimeout = TimeSpan.FromMinutes(1.0d);
            netTcpBinding.CloseTimeout = TimeSpan.FromMinutes(1.0d);
            netTcpBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.None;

            EndpointAddress endpointAddress = new EndpointAddress(url);
            ChannelFactory = new DuplexChannelFactory<IService>(typeof(PriceCallBack), netTcpBinding, endpointAddress);
            //ChannelFactory = new DuplexChannelFactory<IService>(typeof(PriceCallBack), "WSDualHttpBinding_IService");

            //ChannelFactory.Credentials.UserName.UserName = "VNDS";
            //ChannelFactory.Credentials.UserName.Password = "VNDS";

            //ChannelFactory.Open();
        }

       

        private void KeepLive(object sender, ElapsedEventArgs e)
        {
            //tao ham de keep live với server

        }

        /// <summary>
        /// Inits the price server.
        /// </summary>
        /// <param name="uri">The URI.</param>
        [Obsolete("Use in external library!")]
        private void InitPriceServer(string uri)
        {
            try
            {
                // Construct InstanceContext to handle messages on callback interface.
                InstanceContext instanceContext = new InstanceContext(this);

                NetTcpBinding netTcpBinding = new NetTcpBinding(SecurityMode.Transport);
                //netTcpBinding.MaxBufferPoolSize = int.MaxValue;
                netTcpBinding.MaxReceivedMessageSize = netTcpBinding.MaxBufferSize = int.MaxValue;

                //Connect
                //PriceServer = new ServiceClient(instanceContext);
                PriceServer = new ServiceClient(instanceContext, netTcpBinding,
                    new EndpointAddress(uri));

                //PriceServer.Open();

                PriceServer.Subscribe(Key, (List<string>)Tickers);
            }
            catch (Exception ex)
            {
                _log.Error("Error when inits Price server in client", ex);
            }
            finally
            {

            }
        }
        #endregion
    }
}
